""" S3D.ElectricCraft.Game package """
from PyQt4.QtCore import QObject, SIGNAL
import time

VIEW_ITEM_WIRE          = 0
VIEW_ITEM_BRIDGE        = 1
VIEW_ITEM_KEY           = 2
VIEW_ITEM_DIODE         = 3
VIEW_ITEM_INPUT         = 4
VIEW_ITEM_OUTPUT        = 5
VIEW_ITEM_POWER         = 6
VIEW_ITEM_CHIP          = 7
VIEW_ITEM_CHIP_INPUT    = 8
VIEW_ITEM_CHIP_OUTPUT   = 9
VIEW_ITEM_MARK          = 10
VIEW_ITEMS_COUNT        = 11

def getViewItems():
    return [name for name in globals() if name.startswith("VIEW_ITEM_")]

VIEW_ITEM_NAMES         = getViewItems()
VIEW_ITEM_NAMES.sort(key = lambda x: globals()[x])

DIR_LIST                = ((-1, 0), (0, -1), (1, 0), (0, 1))
DIR_NAMES               = ("left", "up", "right", "down")
DIRS_COUNT              = 4

FULL_CONSTR             = (True,  True,  True,  True)
EMPTY_CONSTR            = (False, False, False, False)
LEFT_CONSTR             = (False, True,  True,  True)
RIGHT_CONSTR            = (True,  True,  False, True)

CHIP_PARTS              = (VIEW_ITEM_CHIP_INPUT, VIEW_ITEM_CHIP_OUTPUT)
CHIP_PART_CONSTRS       = (
    LEFT_CONSTR,
    RIGHT_CONSTR,
)

INPUT_OUTPUT_PARTS      = (VIEW_ITEM_INPUT, VIEW_ITEM_OUTPUT, VIEW_ITEM_MARK)

DIODE_CONSTR            = (
    (False, True, False, True),
    (True, False, True, False),
    (False, True, False, True),
    (True, False, True, False),
)

def getRevertedDir(index):
    return (index + 2) % DIRS_COUNT

def getRotatedDir(index):
    return (index + 1) % DIRS_COUNT

class ViewGroup:
    __slots__ = ("state", "switch")

    def __init__(self):
        self.state  = False
        self.switch = False

class ViewItem:
    def __init__(self, type, constr = None, **kw):
        self.type           = type
        self.group          = None if type != VIEW_ITEM_BRIDGE else [None, None]

        if type == VIEW_ITEM_WIRE:
            self.constr     = constr or FULL_CONSTR # manually attach wires
        elif type == VIEW_ITEM_DIODE:
            self.direction  = kw["direction"]
            self.constr     = DIODE_CONSTR[self.direction]
        elif type == VIEW_ITEM_KEY:
            self.direction  = kw["direction"] % 2 # only two directions, left-right & up-down
            self.state      = kw["state"]         # key state, False - opened, True - closed
            self.constr     = EMPTY_CONSTR
        elif type == VIEW_ITEM_CHIP:
            self.jumpers    = kw.get("jumpers") or 3
            self.model      = kw.get("model")
            self.constr     = FULL_CONSTR
        elif type in CHIP_PARTS:
            self.index      = kw["index"]
            self.chip       = kw["chip"]
            self.constr     = CHIP_PART_CONSTRS[type - VIEW_ITEM_CHIP - 1]
        elif type == VIEW_ITEM_MARK:
            self.parent     = kw["parent"]
            self.constr     = FULL_CONSTR
        else:
            if type == VIEW_ITEM_INPUT or type == VIEW_ITEM_OUTPUT:
                self.index  = kw.get("index")
            self.constr     = constr or EMPTY_CONSTR # no constraints by default

    def copy(self):
        if self.type == VIEW_ITEM_DIODE:
            return ViewItem(self.type, self.constr, direction = self.direction)

        if self.type == VIEW_ITEM_KEY:
            return ViewItem(self.type, self.constr, direction = self.direction, state = self.state)

        if self.type != VIEW_ITEM_WIRE and self.constr == FULL_CONSTR:
            return ViewItem(self.type)

        return ViewItem(self.type, self.constr)

    def resetConstr(self, dir, revertConstr = False):
        if revertConstr:
            dir = getRevertedDir(dir)

        if not self.constr[dir]:
            return

        constr = list(self.constr)
        constr[dir] = False
        self.constr = tuple(constr)

class ViewModel(QObject):
    __slots__ = ("sizeX", "sizeY", "items", "isPreview", "groups", "maxInputs", "inputs", "outputs")

    def __new__(cls, *args, **kw):
        import sip
        obj = sip.wrapper.__new__(cls)
        super(cls, obj).__init__()
        return obj

    def __init__(self, name, maxInputs, sizeX, sizeY):
        self.name       = name
        self.maxInputs  = maxInputs
        self.sizeX      = sizeX
        self.sizeY      = sizeY
        self.items      = [[None for _ in range(sizeY)] for _ in range(sizeX)]
        self.isPreview  = False
        self.inputs     = []
        self.outputs    = []
        self.groups     = []

    def setItem(self, x, y, item):
        prevItem = self.items[x][y]
        if prevItem is item:
            return

        if item is None:
            if prevItem.type == VIEW_ITEM_INPUT:
                self.inputs[prevItem.index] = None
                while len(self.inputs) > 0 and self.inputs[-1] is None:
                    self.inputs.pop()
            elif prevItem.type == VIEW_ITEM_OUTPUT:
                self.outputs[prevItem.index] = None
                while len(self.outputs) > 0 and self.outputs[-1] is None:
                    self.outputs.pop()

        elif item.type == VIEW_ITEM_INPUT and item.index is None:
            # automatically find index
            for index, p in enumerate(self.inputs):
                if p is None:
                    item.index = index
                    self.inputs[index] = item
                    break
                    
            if item.index is None:
                item.index = len(self.inputs)
                self.inputs.append(item)

        elif item.type == VIEW_ITEM_OUTPUT and item.index is None:
            # automatically find index
            for index, p in enumerate(self.outputs):
                if p is None:
                    item.index = index
                    self.outputs[index] = item
            if item.index is None:
                item.index = len(self.outputs)
                self.outputs.append(item)

        self.items[x][y] = item
        self.emit(SIGNAL("ModelChanged()"))

    def setGroupState(self, index, state):
        self.groups[index].state = state
        self.emit(SIGNAL("ModelChanged()"))

    def getNeighbourWireGroup(self, x, y, dir):
        if x not in range(self.sizeX) or y not in range(self.sizeY):
            return

        item = self.items[x][y]
        if item is None or item.constr[dir]:
            return None

        dx, dy = DIR_LIST[dir]
        nx, ny = x + dx, y + dy
        if nx < 0 or nx >= self.sizeX or ny < 0 or ny >= self.sizeY:
            return None

        next = self.items[nx][ny]
        if next is None:
            return None

        invDir = getRevertedDir(dir)
        if next.constr[invDir]:
            return None

        if next.type == VIEW_ITEM_WIRE:
            return next.group
        if next.type == VIEW_ITEM_BRIDGE:
            return next.group[dir % 2]
        return None

    def update(self):
        self.emit(SIGNAL("ModelChanged()"))
        